#!/usr/bin/perl
# 
#                           queue_refresh.pl
#
#       Background process which loops, doing the following:
#
#       1. Trims old played tracks off of the end of the playlist
#       2. Watches the mpd playlist for changes and writes them to
#          an xml file for reference by the site's CGI dispatcher
#          to be used when displaying the queue to users.
#       3. Also includes number of users connected to mpd's http port.
#
#       Last modified: 02/02/12
#

use warnings;
use strict;

use Audio::MPD;
use Storable;
use Encode;
use DBI;
use Digest;


my $PLAYED_TRACKS_CUTOFF = 30;  # Maximum number of previously played tracks to report
my $SLEEP_INTERVAL = 5;

my $mpd = Audio::MPD->new;
our $TARGET_TEMP = '/tmp/current_playlist_status.temp';
our $TARGET_OUT = '/tmp/current_playlist_status.storable';
my $PORT = 9999;

sub write_queue {
    if ($mpd->status->state ne 'play') { return undef; }
    my @playlist = reverse $mpd->playlist->as_items;
    my ($num_played, $queued) = (0,0);
    my $pos;
    my @pl = ();
    my %storage;

    open my $f_out, '>', $TARGET_OUT;
    my $users_connected = users_connected();
    $users_connected or $users_connected = 0;
    $storage{connections} = $users_connected;

    my $cur_pos = $mpd->current->pos;
    for my $t (@playlist) {
        my $play_status = ($t->pos <=> $cur_pos);
        if ($play_status < 0) {
            $pos = "played";
            $num_played++;
        }
        elsif ($play_status == 0) {
            $pos = "current";
        }
        elsif ($play_status > 0) {
            $pos = "queued";
            $queued++;
        }
        my $digest = Digest->new('MD5');
        $digest->add(encode_utf8($t->as_string));
        my $title_digest = $digest->hexdigest;

        push @pl, write_track($t, $pos, $title_digest, $t->file);
    }
    $storage{pl} = \@pl; 
    $storage{num_queued} = $queued; 
    store \%storage, $TARGET_TEMP;
    rename $TARGET_TEMP, $TARGET_OUT;
    return $num_played;
}

sub write_track {
    my ($t, $pos, $digest) = @_;
    my ($time_sofar, $time_left) = ('','');

    if ($pos eq "current") {
        $time_sofar = $mpd->status->time->sofar;
        $time_left  = $mpd->status->time->left;
    }
    return { 'artist'         => xml_esc($t->artist) || '',
             'title'          => xml_esc($t->title)  || '',
             'time_total'     => minsec($t->time),
             'time_sofar'     => $time_sofar,
             'time_left'      => $time_left,
             'pos'            => $pos,
             'path'           => encode_utf8($t->file),
             'string_digest'  => $digest };
}

sub minsec {
    return int($_[0] / 60) . ':' . sprintf("%02d", ($_[0] % 60));
}

sub random_track {
    my ($path_sth, $len_sth) = @_;
    $len_sth->execute();
    my $num_tracks = ${ $len_sth->fetchrow_arrayref }[0];

    $path_sth->execute(int rand $num_tracks);
    my $path = ${ $path_sth->fetchrow_arrayref }[0];
    return $path;
}

sub users_connected {
    my @users_connected = `netstat -aln | grep :$PORT | grep 'ESTABLISHED'`;

    # Ignores additional entries for users with multiple ports held open.
    my %users = ();
    for (@users_connected) {
        my $ip = (split ':', (split[4]))[0];
        $users{$ip} = 1; 
    }
    my $count = 0;
    $count++ for keys %users;
    return $count;
}

sub xml_esc {
    my $_ = shift;
    return if !$_;
    s/&/&amp;/g;
    s/</&lt;/g;
    s/>/&gt;/g;
    s/"/&quot;/g;
    s/'/&apos;/g;
    return $_;
}

my $target_db = "/home/o0/jpsr/song_db.sqlite";
my $dbh = DBI->connect ("dbi:SQLite:dbname=$target_db", "", "", {RaiseError => 1})
    or die $DBI::errstr;

my $path_sth = $dbh->prepare("SELECT path FROM tracks WHERE id=:1"); 
my $len_sth  = $dbh->prepare("SELECT COUNT(*) FROM tracks"); 

for (;;) {
    my $pl_len = $mpd->playlist->as_items - 1;

    # Queue random track if we have reached the end of the playlist and resume
    # playing with random track if playback has stopped for some reason.
    if ($pl_len == $mpd->current->pos) {
        eval {
            my $path = random_track($path_sth, $len_sth);
            system(qq[ mpc add "$path"]);
        };
    }
    
    if ($mpd->status->state ne 'play') {
        $mpd->play();
        $mpd->playlist->crop();
    }

    # Write current playlist to XML file
    my $played_tracks = write_queue();
    
    if ($played_tracks) {
        # Crop old played tracks from queue
        my $crop_to_pos = $mpd->current->pos - $PLAYED_TRACKS_CUTOFF;
        $mpd->playlist->delete(0 .. $crop_to_pos);

    }

    sleep $SLEEP_INTERVAL;
}


